﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using EvoSim.RandomNumberGenerators;
using System.Diagnostics;
using EvoSim.Simulation.Genes;
using EvoSim.Simulation.NeuralNets.SpikeNet;
using EvoSim.Simulation.NeuralNets.RMP;
using EvoSim.Simulation.NeuralNets;

namespace EvoSim.Simulation
{
	public class Creature : Entity
	{
		public INeuralNet Brain;
		public Genome CreatureGenome;

		public int EyeNeurons;
		public double EyeSpan = Math.PI;
		public int ViewDistance = 1000;
		//EntityColor
		public double Speed;
		public double RotationSpeed;
		public DoubleMinMax Energy = new DoubleMinMax(0, 0);

		public bool CanSeeCreatures = true;

		public double EnergyLossByTick = 1;
		public double EnergyLossByRotation = 0.1;
		public double EnergyLossByMove = 0.1;
		public double EnergyLossByEating = 0.1;
		public double EnergyLossByMating = 60 * 15;
		public double EatingBiteSize = 100;

		public Creature MatingCandidate;
		public int CyclesInMating = 0;
		public int CyclesNeededForMating = 1;

		public int CooldownCyclesLeft = 0;
		public int MatingCooldown = 60 * 10;

		public int MaxAge = 60 * 60 * 4;

		#region Neurons
		public IInputNeuron[] EyeR_I;
		public IInputNeuron[] EyeG_I;
		public IInputNeuron[] EyeB_I;
		public IInputNeuron[] EyeDistance_I;

		protected IInputNeuron random_I;
		protected IInputNeuron clock_I;
		protected IInputNeuron energy_I;

		protected IOutputNeuron rotate_O;
		protected IOutputNeuron move_O;
		protected IOutputNeuron eat_O;
		protected IOutputNeuron mate_O;

		#endregion

		public double PixelsRotatedLeft;
		public double PixelsRotatedRight;
		public double PixelsMovedForwards;
		public double PixelsMovedBackwards;
		public int TimesHaveEaten;
		public int TimesMated;

		public bool LoseEnergy = true;
		public bool IsThinking = true;
		public bool DisableMating = false;

		public bool AlwaysMate = false;
		public bool AlwaysEat = false;

		public Creature()
			: base("Creature")
		{
			CooldownCyclesLeft = MatingCooldown;
		}

		public virtual void Randomize()
		{
			//No BodyGenes

			CreatureGenome.Randomize(5, 3, 4, 20);

			int i = 0;
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Rotate";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Move";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Eat";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Mate";

			((DoubleGene)((DefaultSigmoid)((RMP_NeuronGene)CreatureGenome.NetChromosome.Outputs[0]).SigmoidFunction).GeneList[0]).Value = -1;
			((DefaultSigmoid)((RMP_NeuronGene)CreatureGenome.NetChromosome.Outputs[0]).SigmoidFunction).GenesHasChanged();
			//((DoubleGene)((DefaultSigmoid)((RMP_NeuronGene)CreatureGenome.NetChromosome.Outputs[1]).SigmoidFunction).GeneList[0]).Value = -1;

			ImprintGenome(CreatureGenome);
			Energy.SetToMax();
		}

		public virtual Creature GetNewInstance()
		{
			return new Creature();
		}

		public bool CheckMateValidity(Creature parent2)
		{
			if (parent2.CreatureGenome.ID == CreatureGenome.Parent1 ||
				parent2.CreatureGenome.ID == CreatureGenome.Parent2)
				return false;

			if (parent2.CreatureGenome.Parent1 != null)
			{
				if (parent2.CreatureGenome.Parent1 == CreatureGenome.Parent1 ||
					parent2.CreatureGenome.Parent1 == CreatureGenome.Parent2 ||
					parent2.CreatureGenome.Parent1 == CreatureGenome.ID)
					return false;
			}

			if (parent2.CreatureGenome.Parent2 != null)
			{
				if (parent2.CreatureGenome.Parent2 == CreatureGenome.Parent1 ||
					parent2.CreatureGenome.Parent2 == CreatureGenome.Parent2 ||
					parent2.CreatureGenome.Parent2 == CreatureGenome.ID)
					return false;
			}

			return true;
		}
		public Creature CreateOffspring(Creature parent2, double posX, double posY)
		{
			return CreateOffspring(GetNewInstance(), parent2, posX, posY);
		}
		public virtual Creature CreateOffspring(Creature child, Creature parent2, double posX, double posY)
		{
			var parent1 = this;
			child.World = parent1.World;
			child.ImprintGenome(parent1.CreatureGenome.GetChildGenome(parent2.CreatureGenome));
			child.Position = new EntityPosition(posX, posY);

			Energy.Value /= 2;
			child.Energy.Value = Energy.Value;

			return child;
		}

		protected virtual void HandleInputNeurons()
		{
			HandleEyeInput();

			random_I.FireFrequency = Globals.Random.NextDouble(0, 1);

			//clock_I
			energy_I.FireFrequency = 1 - Energy.Value / Energy.Max;
		}
		protected virtual void HandleEyeInput()
		{
			const double tau = Math.PI * 2;

			double rotationStep = EyeSpan / EyeNeurons;
			double rotation = (Rotation + EyeSpan / 2);

			if (rotation < 0)
				rotation = tau + rotation;
			else if (rotation > tau)
				rotation = rotation - tau;

			var entityCollection = new List<Entity>();
			int regionsToAdd = (int)Math.Ceiling((double)ViewDistance / World.RegionSize);
			int roofX = Math.Min(Region.X + regionsToAdd, World.RegionLengthX);
			int roofY = Math.Min(Region.Y + regionsToAdd, World.RegionLengthY);
			for (int rX = Math.Max(0, Region.X - regionsToAdd); rX < roofX; rX++)
			{
				for (int rY = Math.Max(0, Region.Y - regionsToAdd); rY < roofY; rY++)
				{
					entityCollection.AddRange(World.Regions[rX, rY]);
				}
			}

			for (int i = 0; i < EyeNeurons; i++)
			{
				double distance = -1;
				double r = -1;
				double g = -1;
				double b = -1;

				foreach (Entity entity in entityCollection)
				{
					if (entity == this)
						continue;

					if (!CanSeeCreatures && entity.EntityName == "Creature")
						continue;

					double dx = entity.Position.X - Position.X;
					double dy = entity.Position.Y - Position.Y;
					double entityDistance = Math.Sqrt(dx * dx + dy * dy);

					double angle = Math.Acos(dx / entityDistance);

					if (dy < 0)
						angle = tau - angle;

					double angleDifference = rotation - angle;

					if (angleDifference < 0)
						angleDifference = tau + angleDifference;

					if (angleDifference >= 0 &&
						angleDifference < rotationStep &&
						entityDistance < ViewDistance &&
						(distance == -1 || entityDistance < distance))
					{
						distance = entityDistance;
						r = entity.EntityColor.R / (double)255;
						g = entity.EntityColor.G / (double)255;
						b = entity.EntityColor.B / (double)255;
					}
				}

				int n = EyeNeurons - i - 1;

				if (distance != -1)
					EyeDistance_I[n].FireFrequency = 1 - distance / ViewDistance;
				else
					EyeDistance_I[n].FireFrequency = -1;
				EyeR_I[n].FireFrequency = Math.Max(0, r);
				EyeG_I[n].FireFrequency = Math.Max(0, g);
				EyeB_I[n].FireFrequency = Math.Max(0, b);

				rotation -= rotationStep;
				if (rotation < 0)
					rotation = tau + rotation;
				else if (rotation > tau)
					rotation = rotation - tau;
			}
		}

		public virtual void ImprintGenome(Genome genome)
		{
			CreatureGenome = genome;

			EyeNeurons = genome.NetChromosome.EyeNeuronsCount;

			Brain = genome.NetChromosome.GetNeuralNet();
			genome.NetChromosome.Mutate();
			int i = 0;
			EyeR_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);
			EyeG_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);
			EyeB_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);
			EyeDistance_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);

			random_I = Brain.Inputs[i++];
			clock_I = Brain.Inputs[i++];
			energy_I = Brain.Inputs[i++];

			i = 0;
			rotate_O = Brain.Outputs[i++];
			move_O = Brain.Outputs[i++];
			eat_O = Brain.Outputs[i++];
			mate_O = Brain.Outputs[i++];

			i = 0;
			//BodyGenes
		}

		public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
		{
			base.Update(gameTime);

			Energy.Value -= EnergyLossByTick;

			HandleInputNeurons();

			if (IsThinking)
				Brain.Update();

			//Rotate
			double rotationDistance = rotate_O.Output * RotationSpeed;
			Rotation -= rotationDistance;
			Energy.Value -= rotationDistance * EnergyLossByRotation;
			PixelsRotatedLeft += rotationDistance;

			//Move
			double moveDistance = move_O.Output * Speed;
			Move(moveDistance * Math.Cos(Rotation), moveDistance * Math.Sin(Rotation));
			Energy.Value -= Math.Abs(moveDistance) * EnergyLossByMove;
			PixelsMovedForwards += moveDistance;

			//Eat
			if ( AlwaysEat || (eat_O.Output > 0.5))
			{
				Energy.Value -= EnergyLossByEating;

				foreach (Entity entity in World.EntityList)
				{
					if (entity == this)
						continue;

					var entityRect = new Rectangle((int)entity.Position.X - entity.Radius / 2, (int)entity.Position.Y - entity.Radius / 2, entity.Radius, entity.Radius);
					var thisRect = new Rectangle((int)Position.X - Radius / 2, (int)Position.Y - Radius / 2, Radius, Radius);

					if (entityRect.Intersects(thisRect) &&
						entity.EntityName == "Food")
					{
						Food food = (Food)entity;

						TimesHaveEaten += 1;

						if (food.EnergyStorage >= EatingBiteSize)
						{
							food.EnergyStorage -= EatingBiteSize;
							Energy.Value += EatingBiteSize;
						}
						else
						{
							Energy.Value += food.EnergyStorage;
							food.EnergyStorage = 0;
						}
					}
				}
			}
			//Mate
			if ( (AlwaysMate || (!DisableMating && mate_O.Output > 0.5) ) && CooldownCyclesLeft == 0)
			{
				if (MatingCandidate != null)
				{
					var entityRect = new Rectangle((int)MatingCandidate.Position.X, (int)MatingCandidate.Position.Y, MatingCandidate.Radius, MatingCandidate.Radius);
					var thisRect = new Rectangle((int)Position.X, (int)Position.Y, Radius, Radius);

					if (entityRect.Intersects(thisRect))
						CyclesInMating += 1;

					if (CyclesInMating >= CyclesNeededForMating)
					{
						TimesMated += 1;

						Energy.Value -= EnergyLossByMating;
						World.AddEntity(CreateOffspring(MatingCandidate, this.Position.X, this.Position.Y));
						MatingCandidate = null;
						CyclesInMating = 0;
						CooldownCyclesLeft = MatingCooldown;
					}
				}
				else
				{
					CyclesInMating = 0;

					foreach (Entity entity in World.EntityList)
					{
						if (entity == this)
							continue;

						Rectangle entityRect = new Rectangle((int)entity.Position.X, (int)entity.Position.Y, entity.Radius, entity.Radius);
						Rectangle thisRect = new Rectangle((int)Position.X, (int)Position.Y, Radius, Radius);

						if (entity.EntityName == "Creature")
						{
							var c = (Creature)entity;

							if (CheckMateValidity(c) && entityRect.Intersects(thisRect) &&
								c.MatingCandidate != this)
							{
								MatingCandidate = c;
							}
						}
					}
				}
			}

			if (CooldownCyclesLeft != 0)
				CooldownCyclesLeft -= 1;

			if (!LoseEnergy)
				Energy.SetToMax();

			if (Energy.Value <= 1000 || Age >= MaxAge)
				Kill();
		}

		protected static double GetDoubleFromGene(DoubleGene gene, double max)
		{
			return GetDoubleFromGene(gene, 0, max);
		}
		protected static double GetDoubleFromGene(DoubleGene gene, double min, double max)
		{
			return min + (max - min) * gene.Value;
		}
		protected static int GetIntFromGene(DoubleGene gene, int max)
		{
			return GetIntFromGene(gene, 0, max);
		}
		protected static int GetIntFromGene(DoubleGene gene, int min, int max)
		{
			return min + (int)Math.Round((max - min) * gene.Value, 0);
		}
		protected static T[] GetListSliceAsArray<T>(int from, int to, ICollection<T> l)
		{
			return l.Skip(from).Take(to - from).Cast<T>().ToArray();
		}
	}
}
